//
//  DMScannerView.swift
//  DigitalMuseum
//
//  Created by zhongshan on 2024/2/21.
//

import UIKit
import AVFoundation

struct DMScanResult {
    let content: String
    let rect: CGRect
}

protocol DMScannerViewDelegate: AnyObject {
    func scannerView(_ scannerView: DMScannerView, didScanOutResults results: [DMScanResult])
    func scannerView(_ scannerView: DMScannerView, didChooseResult result: DMScanResult)
    
    func scannerViewDidStartRunning(_ scannerView: DMScannerView)
    func scannerViewDidStopRunning(_ scannerView: DMScannerView)
}

final class DMScannerView: UIView {
    weak var delegate: (any DMScannerViewDelegate)?
    
    /// 是否正在扫描检测
    var isRunning: Bool {
        session.isRunning == true
    }
    
    /// 摄像头当前是否可用
    var isCameraAvailable: Bool {
        let authStatus = AVCaptureDevice.authorizationStatus(for: .video)
        return authStatus != .restricted && authStatus != .denied
    }
    
    private var previewLayer: AVCaptureVideoPreviewLayer {
        return self.layer as! AVCaptureVideoPreviewLayer
    }
    
    private var detectedResults: [DMScanResult]?
    private let anchorContainer: UIView = UIView()
    
    override class var layerClass: AnyClass {
        return AVCaptureVideoPreviewLayer.self
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        addSubview(anchorContainer)
        anchorContainer.translatesAutoresizingMaskIntoConstraints = false
        anchorContainer.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        anchorContainer.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
        anchorContainer.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        anchorContainer.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        stop()
    }
    
    func start() {
        if !isCameraAvailable { return }
        
        previewLayer.session = session
        previewLayer.videoGravity = .resizeAspectFill
        
        DispatchQueue.global().async {
            self.session.startRunning()
        }
        
        clearContainer()
        detectedResults = nil
        delegate?.scannerViewDidStartRunning(self)
    }
    
    func stop() {
        if !isRunning { return }
        if !isCameraAvailable { return }
        
        session.stopRunning()
        delegate?.scannerViewDidStopRunning(self)
    }
    
    private func clearContainer() {
        while !anchorContainer.subviews.isEmpty {
            anchorContainer.subviews.last?.removeFromSuperview()
        }
    }
    
    private lazy var session: AVCaptureSession = {
        let session = AVCaptureSession()
        session.sessionPreset = .high
        
        if let device = device, let input = try? AVCaptureDeviceInput(device: device), session.canAddInput(input) {
            session.addInput(input)
        }
        
        let metadataOutput = AVCaptureMetadataOutput()
        if session.canAddOutput(metadataOutput) {
            session.addOutput(metadataOutput)
            metadataOutput.metadataObjectTypes = [.qr]
            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
        }
        
        return session
    }()
    
    private lazy var device: AVCaptureDevice? = {
        guard let device =  AVCaptureDevice.default(for: .video) else { return nil }
        do {
            try device.lockForConfiguration()
            if  device.isFocusModeSupported(.continuousAutoFocus) {
                device.focusMode = .continuousAutoFocus
            }
            if device.isWhiteBalanceModeSupported(.continuousAutoWhiteBalance) {
                device.whiteBalanceMode = .continuousAutoWhiteBalance
            }
            if device.isExposureModeSupported(.continuousAutoExposure) {
                device.exposureMode = .continuousAutoExposure
            }
            device.unlockForConfiguration()
        } catch {
            print(error)
        }
        return device
    }()

}

// MARK: - AVCaptureMetadataOutputObjectsDelegate
extension DMScannerView: AVCaptureMetadataOutputObjectsDelegate {
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        let width = self.bounds.width
        let height = self.bounds.height
        var results = [DMScanResult]()
        for metaObj in metadataObjects {
            if let codeObj = metaObj as? AVMetadataMachineReadableCodeObject, codeObj.stringValue?.isEmpty == false {
                let rectOfView = CGRect(x: width - codeObj.bounds.minY * width - codeObj.bounds.height * width,
                                        y: codeObj.bounds.minX * height, width: codeObj.bounds.height * width, height: codeObj.bounds.width * height)
                results.append(DMScanResult(content: codeObj.stringValue!, rect: rectOfView))
            }
        }
        if !results.isEmpty {
            stop()
            drawCenterAnchor(with: results)
        }
    }
    
    private func drawCenterAnchor(with results: [DMScanResult]) {
        self.detectedResults = results
        AudioServicesPlaySystemSound(1519)
        clearContainer()
        
        for (idx, qrResult) in results.enumerated() {
            let circleAnchor = UIButton(type: .custom)
            let circleAnchorWidth: CGFloat = 35
            circleAnchor.frame = CGRect(x: qrResult.rect.midX - circleAnchorWidth/2, y: qrResult.rect.midY - circleAnchorWidth/2, width: circleAnchorWidth, height: circleAnchorWidth)
            circleAnchor.setImage(UIImage(resource: .scannerJump), for: .normal)
            circleAnchor.tag = 666 + idx
            circleAnchor.addTarget(self, action: #selector(handleCircleAnchorClick(_:)), for: .touchUpInside)
            anchorContainer.addSubview(circleAnchor)
        }
        bringSubviewToFront(anchorContainer)
        
        delegate?.scannerView(self, didScanOutResults: results)
        
        if results.count == 1 {
            delegate?.scannerView(self, didChooseResult: results[0])
        }
    }
    
    @objc
    private func handleCircleAnchorClick(_ sender: UIButton) {
        let idx = sender.tag - 666
        if let detectedResults, idx < detectedResults.count {
            delegate?.scannerView(self, didChooseResult: detectedResults[idx])
            self.detectedResults = nil
        }
    }
    
}

// MARK: - Torch
extension DMScannerView {
    
    /// 手电筒是否打开（这个值有延迟，同一个runloop下switchTorch后，获取的值不变）/
    var isTorchActive: Bool {
        guard let torchDevice = AVCaptureDevice.default(for: .video), torchDevice.isTorchAvailable else { return false }
        do {
            try torchDevice.lockForConfiguration()
            let res = torchDevice.isTorchActive
            torchDevice.unlockForConfiguration()
            return res
        } catch {
            return false
        }
    }
    
    /// 打开/关闭手电筒
    func switchTorch() {
        guard let torchDevice = AVCaptureDevice.default(for: .video), torchDevice.isTorchAvailable else { return }
        do {
            try torchDevice.lockForConfiguration()
            if torchDevice.isTorchActive {
                if torchDevice.isTorchModeSupported(.off) {
                    torchDevice.torchMode = .off
                }
            } else {
                if torchDevice.isTorchModeSupported(.on) {
                    torchDevice.torchMode = .on
                }
            }
            torchDevice.unlockForConfiguration()
        } catch {
            print(error.localizedDescription)
        }
    }
}
